{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "#skip\n",
    "! [ -e /content ] && pip install -Uqq fastai  # upgrade fastai on colab"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "from fastai.basics import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#hide\n",
    "from nbdev.showdoc import *"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#default_exp callback.progress"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Progress and logging callbacks\n",
    "\n",
    "> Callback and helper function to track progress of training or log results"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from fastai.test_utils import *"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ProgressCallback -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# export\n",
    "@docs\n",
    "class ProgressCallback(Callback):\n",
    "    \"A `Callback` to handle the display of progress bars\"\n",
    "    _stateattrs=('mbar','pbar')\n",
    "    run_after=Recorder\n",
    "\n",
    "    def before_fit(self):\n",
    "        assert hasattr(self.learn, 'recorder')\n",
    "        if self.create_mbar: self.mbar = master_bar(list(range(self.n_epoch)))\n",
    "        if self.learn.logger != noop:\n",
    "            self.old_logger,self.learn.logger = self.logger,self._write_stats\n",
    "            self._write_stats(self.recorder.metric_names)\n",
    "        else: self.old_logger = noop\n",
    "\n",
    "    def before_epoch(self):\n",
    "        if getattr(self, 'mbar', False): self.mbar.update(self.epoch)\n",
    "\n",
    "    def before_train(self):    self._launch_pbar()\n",
    "    def before_validate(self): self._launch_pbar()\n",
    "    def after_train(self):     self.pbar.on_iter_end()\n",
    "    def after_validate(self):  self.pbar.on_iter_end()\n",
    "    def after_batch(self):\n",
    "        self.pbar.update(self.iter+1)\n",
    "        if hasattr(self, 'smooth_loss'): self.pbar.comment = f'{self.smooth_loss:.4f}'\n",
    "\n",
    "    def _launch_pbar(self):\n",
    "        self.pbar = progress_bar(self.dl, parent=getattr(self, 'mbar', None), leave=False)\n",
    "        self.pbar.update(0)\n",
    "\n",
    "    def after_fit(self):\n",
    "        if getattr(self, 'mbar', False):\n",
    "            self.mbar.on_iter_end()\n",
    "            delattr(self, 'mbar')\n",
    "        if hasattr(self, 'old_logger'): self.learn.logger = self.old_logger\n",
    "\n",
    "    def _write_stats(self, log):\n",
    "        if getattr(self, 'mbar', False): self.mbar.write([f'{l:.6f}' if isinstance(l, float) else str(l) for l in log], table=True)\n",
    "\n",
    "    _docs = dict(before_fit=\"Setup the master bar over the epochs\",\n",
    "                 before_epoch=\"Update the master bar\",\n",
    "                 before_train=\"Launch a progress bar over the training dataloader\",\n",
    "                 before_validate=\"Launch a progress bar over the validation dataloader\",\n",
    "                 after_train=\"Close the progress bar over the training dataloader\",\n",
    "                 after_validate=\"Close the progress bar over the validation dataloader\",\n",
    "                 after_batch=\"Update the current progress bar\",\n",
    "                 after_fit=\"Close the master bar\")\n",
    "\n",
    "if not hasattr(defaults, 'callbacks'): defaults.callbacks = [TrainEvalCallback, Recorder, ProgressCallback]\n",
    "elif ProgressCallback not in defaults.callbacks: defaults.callbacks.append(ProgressCallback)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: left;\">\n",
       "      <th>epoch</th>\n",
       "      <th>train_loss</th>\n",
       "      <th>valid_loss</th>\n",
       "      <th>time</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>0</td>\n",
       "      <td>12.347102</td>\n",
       "      <td>12.212431</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1</td>\n",
       "      <td>10.881455</td>\n",
       "      <td>8.909552</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2</td>\n",
       "      <td>9.235189</td>\n",
       "      <td>5.996475</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>3</td>\n",
       "      <td>7.660278</td>\n",
       "      <td>3.935604</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>4</td>\n",
       "      <td>6.281022</td>\n",
       "      <td>2.550308</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "learn = synth_learner()\n",
    "learn.fit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "#export\n",
    "@patch\n",
    "@contextmanager\n",
    "def no_bar(self:Learner):\n",
    "    \"Context manager that deactivates the use of progress bars\"\n",
    "    has_progress = hasattr(self, 'progress')\n",
    "    if has_progress: self.remove_cb(self.progress)\n",
    "    try: yield self\n",
    "    finally:\n",
    "        if has_progress: self.add_cb(ProgressCallback())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "[0, 19.08680534362793, 15.95688533782959, '00:00']\n",
      "[1, 16.612403869628906, 11.260307312011719, '00:00']\n",
      "[2, 13.902640342712402, 7.271045684814453, '00:00']\n",
      "[3, 11.376837730407715, 4.473145484924316, '00:00']\n",
      "[4, 9.196401596069336, 2.684819221496582, '00:00']\n"
     ]
    }
   ],
   "source": [
    "learn = synth_learner()\n",
    "with learn.no_bar(): learn.fit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#hide\n",
    "#Check validate works without any training\n",
    "def tst_metric(out, targ): return F.mse_loss(out, targ)\n",
    "learn = synth_learner(n_trn=5, metrics=tst_metric)\n",
    "preds,targs = learn.validate()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#hide\n",
    "#Check get_preds works without any training\n",
    "learn = synth_learner(n_trn=5, metrics=tst_metric)\n",
    "preds,targs = learn.validate()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.before_fit\" class=\"doc_header\"><code>ProgressCallback.before_fit</code><a href=\"__main__.py#L8\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.before_fit</code>()\n",
       "\n",
       "Setup the master bar over the epochs"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_fit)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.before_epoch\" class=\"doc_header\"><code>ProgressCallback.before_epoch</code><a href=\"__main__.py#L16\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.before_epoch</code>()\n",
       "\n",
       "Update the master bar"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_epoch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.before_train\" class=\"doc_header\"><code>ProgressCallback.before_train</code><a href=\"__main__.py#L19\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.before_train</code>()\n",
       "\n",
       "Launch a progress bar over the training dataloader"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.before_validate\" class=\"doc_header\"><code>ProgressCallback.before_validate</code><a href=\"__main__.py#L20\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.before_validate</code>()\n",
       "\n",
       "Launch a progress bar over the validation dataloader"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.before_validate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.after_batch\" class=\"doc_header\"><code>ProgressCallback.after_batch</code><a href=\"__main__.py#L23\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.after_batch</code>()\n",
       "\n",
       "Update the current progress bar"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_batch)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.after_train\" class=\"doc_header\"><code>ProgressCallback.after_train</code><a href=\"__main__.py#L21\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.after_train</code>()\n",
       "\n",
       "Close the progress bar over the training dataloader"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_train)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.after_validate\" class=\"doc_header\"><code>ProgressCallback.after_validate</code><a href=\"__main__.py#L22\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.after_validate</code>()\n",
       "\n",
       "Close the progress bar over the validation dataloader"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_validate)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"ProgressCallback.after_fit\" class=\"doc_header\"><code>ProgressCallback.after_fit</code><a href=\"__main__.py#L31\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>ProgressCallback.after_fit</code>()\n",
       "\n",
       "Close the master bar"
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(ProgressCallback.after_fit)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## ShowGraphCallback -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# export\n",
    "class ShowGraphCallback(Callback):\n",
    "    \"Update a graph of training and validation loss\"\n",
    "    run_after,run_valid=ProgressCallback,False\n",
    "\n",
    "    def before_fit(self):\n",
    "        self.run = not hasattr(self.learn, 'lr_finder') and not hasattr(self, \"gather_preds\")\n",
    "        if not(self.run): return\n",
    "        self.nb_batches = []\n",
    "        assert hasattr(self.learn, 'progress')\n",
    "\n",
    "    def after_train(self): self.nb_batches.append(self.train_iter)\n",
    "\n",
    "    def after_epoch(self):\n",
    "        \"Plot validation loss in the pbar graph\"\n",
    "        if not self.nb_batches: return\n",
    "        rec = self.learn.recorder\n",
    "        iters = range_of(rec.losses)\n",
    "        val_losses = [v[1] for v in rec.values]\n",
    "        x_bounds = (0, (self.n_epoch - len(self.nb_batches)) * self.nb_batches[0] + len(rec.losses))\n",
    "        y_bounds = (0, max((max(Tensor(rec.losses)), max(Tensor(val_losses)))))\n",
    "        self.progress.mbar.update_graph([(iters, rec.losses), (self.nb_batches, val_losses)], x_bounds, y_bounds)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: left;\">\n",
       "      <th>epoch</th>\n",
       "      <th>train_loss</th>\n",
       "      <th>valid_loss</th>\n",
       "      <th>time</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>0</td>\n",
       "      <td>23.818842</td>\n",
       "      <td>24.120615</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1</td>\n",
       "      <td>20.847765</td>\n",
       "      <td>16.936844</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2</td>\n",
       "      <td>17.464808</td>\n",
       "      <td>11.186396</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>3</td>\n",
       "      <td>14.341479</td>\n",
       "      <td>7.079573</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>4</td>\n",
       "      <td>11.646442</td>\n",
       "      <td>4.333014</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXcAAAD4CAYAAAAXUaZHAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjMuMSwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy/d3fzzAAAACXBIWXMAAAsTAAALEwEAmpwYAAAvaUlEQVR4nO3dd3hVVbrH8e9K74SEVJIQSkggkNAFaQHpVaQMIso4jtjHNsXxemd0iuM4jlOuFZQRpSiiKCAgSBWl90BCNY30ACmQftb9YwdBh5JyWk7ez/PkSXJy9t5v9gM/Nmuv/S6ltUYIIYRjcbJ1AUIIIcxPwl0IIRyQhLsQQjggCXchhHBAEu5CCOGAXKx5sDZt2ujo6Oibvs+kobCskoLSSkxaE+jtRri/p+ULFEIIO7Rv375CrXVQQ7axarhHR0ezd+/e6/681qT5ZH8Wf19/HKeSSu6JDyHI151FOzO4rU8kL97RHWcnZcWKhRDC9pRS6Q3dxqrhfiNbTxTwlzUppOaW0iPSn9dm9aJvdABaawK83Pj3plNU1NTyyvREXJ1lNEkIIW7E5uFeVWPioUX72JiaT1SAF6/P6sW47qEoZVyhK6V4alQsHm7OvLzuOBXVtfz7zp64uzjbuHIhhLBfNg/3bScK2Jiaz+O3xfDwsI7XDe2Hkzrh6erMC6uOMff9fbx9d288XCXghRDiWmwe7qsPZ9PK05VHh3e66XDLvQPb4+nqzG9XHOGn/9nNO3P64uNu819BCGFB1dXVZGVlUVFRYetSLM7Dw4OIiAhcXV2bvC+bJmNFdS0bjuUxISG83uPoM/tF4enmzFPLDvH0soO8fXcfC1cphLClrKwsfH19iY6O/n641hFprSkqKiIrK4v27ds3eX82vTO59UQBF6tqGZ8Q1qDtJvdoy88GRrMxJZ+SimoLVSeEsAcVFRUEBgY6dLCDcX8xMDDQbP9DsWm4rz6cQ2svV27tGNjgbUd2DaXGpNl+stAClQkh7ImjB/tl5vw9bRbu5VW1bEzJY0y3MFwaMbWxV5Q/rTxd2ZiSb4HqhBCiebtpqiqlIpVSm5VSKUqpo0qpx+tef14pdVYpdbDuY1xDDrz5eD6XqmqZ2MAhmctcnJ0Y2jmILcfzMZmkJ70QwjIuXLjAG2+80eDtxo0bx4ULF8xfUD3V55K5Bnhaa90F6A88opTqWvezf2ite9R9rGnIgb84nEMbHzf6tQ9oYMlX3NYlmKKLVRzKutDofQghxI1cL9xra2tvuN2aNWvw9/e3UFU3d9Nw11rnaK33131dCqQAbZty0EtVNWxMzWNsI4dkLhvaOQgnBZtTZWhGCGEZzzzzDKdPn6ZHjx707duXYcOGMWvWLLp37w7A7bffTu/evYmPj2fevHnfbxcdHU1hYSFpaWl06dKF+++/n/j4eEaNGkV5ebnF627QVEilVDTQE9gFDAQeVUrdA+zFuLo/f41t5gJzAaKiogDYmJJPRbWpwbNkfszfy43e7VqzMTWfp0bFNmlfQgj798KqoxzLLjHrPruG+/H7ifHX/flLL71EcnIyBw8eZMuWLYwfP57k5OTvpysuWLCAgIAAysvL6du3L1OnTiUw8IeTRE6ePMnSpUuZP38+M2bM4JNPPmH27Nlm/T1+rN6XzUopH+AT4AmtdQnwJtAR6AHkAH+/1nZa63la6z5a6z5BQUZTsy8O5xDk607f6MYPyVw2LC6Yo9kl5BY7/gMOQgjb69ev3w/mof/73/8mMTGR/v37k5mZycmTJ/9rm/bt29OjRw8AevfuTVpamsXrrNeVu1LKFSPYF2utPwXQWudd9fP5wOr67KussobNx/O5s1+UWTo83hYXwsvrjn+/TyGE47rRFba1eHt7f//1li1b+Oqrr9ixYwdeXl4kJSVdc566u7v79187OztbZVimPrNlFPAukKK1fvWq168eU5kCJNfngBtT8qisafqQzGWdQ3xo6+/JpmYy7l5YVsnMeTuY8fYOPj94lsqaG9+UEULYlq+vL6Wlpdf8WXFxMa1bt8bLy4vU1FR27txp5equrz5X7gOBu4EjSqmDda89C9yplOoBaCANeKA+B1x1KIdQPw96R7VucLHXopRieFwwy/dlUVFda9fNxL4rvMicBbvJL60g2NeDxz88SIC3G9P7RDCrXxTtAr1vvhMhhFUFBgYycOBAunXrhqenJyEhId//bMyYMbz11lskJCQQGxtL//79bVjpDymtrTdHvGfv3vrimD8xu387fjex6803qKfNx/O59z97WPizfgzt3KDFSm6o1qTZnJrPZwfP0jnEl7lDOjT6H4/9Gee57709KKV4Z04fekT4883pQhbtTOerlHxqTZohnYO465YobosLbtIsIiEcSUpKCl26dLF1GVZz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\n",
      "text/plain": [
       "<Figure size 432x288 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "#slow\n",
    "learn = synth_learner(cbs=ShowGraphCallback())\n",
    "learn.fit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "data": {
      "text/plain": [
       "(tensor([1.3757]), tensor([1.3757]), tensor([1.3757]))"
      ]
     },
     "execution_count": null,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "learn.predict(torch.tensor([[0.1]]))"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## CSVLogger -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# export\n",
    "class CSVLogger(Callback):\n",
    "    run_after=Recorder\n",
    "    \"Log the results displayed in `learn.path/fname`\"\n",
    "    def __init__(self, fname='history.csv', append=False):\n",
    "        self.fname,self.append = Path(fname),append\n",
    "\n",
    "    def read_log(self):\n",
    "        \"Convenience method to quickly access the log.\"\n",
    "        return pd.read_csv(self.path/self.fname)\n",
    "\n",
    "    def before_fit(self):\n",
    "        \"Prepare file with metric names.\"\n",
    "        if hasattr(self, \"gather_preds\"): return\n",
    "        self.path.parent.mkdir(parents=True, exist_ok=True)\n",
    "        self.file = (self.path/self.fname).open('a' if self.append else 'w')\n",
    "        self.file.write(','.join(self.recorder.metric_names) + '\\n')\n",
    "        self.old_logger,self.learn.logger = self.logger,self._write_line\n",
    "\n",
    "    def _write_line(self, log):\n",
    "        \"Write a line with `log` and call the old logger.\"\n",
    "        self.file.write(','.join([str(t) for t in log]) + '\\n')\n",
    "        self.file.flush()\n",
    "        os.fsync(self.file.fileno())\n",
    "        self.old_logger(log)\n",
    "\n",
    "    def after_fit(self):\n",
    "        \"Close the file and clean up.\"\n",
    "        if hasattr(self, \"gather_preds\"): return\n",
    "        self.file.close()\n",
    "        self.learn.logger = self.old_logger"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The results are appended to an existing file if `append`, or they overwrite it otherwise."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: left;\">\n",
       "      <th>epoch</th>\n",
       "      <th>train_loss</th>\n",
       "      <th>valid_loss</th>\n",
       "      <th>time</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <td>0</td>\n",
       "      <td>14.464121</td>\n",
       "      <td>16.486717</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>1</td>\n",
       "      <td>12.813704</td>\n",
       "      <td>12.088200</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>2</td>\n",
       "      <td>10.909700</td>\n",
       "      <td>8.334162</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>3</td>\n",
       "      <td>9.097164</td>\n",
       "      <td>5.539018</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <td>4</td>\n",
       "      <td>7.499405</td>\n",
       "      <td>3.602823</td>\n",
       "      <td>00:00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>"
      ],
      "text/plain": [
       "<IPython.core.display.HTML object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "learn = synth_learner(cbs=CSVLogger())\n",
    "learn.fit(5)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"CSVLogger.read_log\" class=\"doc_header\"><code>CSVLogger.read_log</code><a href=\"__main__.py#L8\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>CSVLogger.read_log</code>()\n",
       "\n",
       "Convenience method to quickly access the log."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(CSVLogger.read_log)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "df = learn.csv_logger.read_log()\n",
    "test_eq(df.columns.values, learn.recorder.metric_names)\n",
    "for i,v in enumerate(learn.recorder.values):\n",
    "    test_close(df.iloc[i][:3], [i] + v)\n",
    "os.remove(learn.path/learn.csv_logger.fname)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"CSVLogger.before_fit\" class=\"doc_header\"><code>CSVLogger.before_fit</code><a href=\"__main__.py#L12\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>CSVLogger.before_fit</code>()\n",
       "\n",
       "Prepare file with metric names."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(CSVLogger.before_fit)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/markdown": [
       "<h4 id=\"CSVLogger.after_fit\" class=\"doc_header\"><code>CSVLogger.after_fit</code><a href=\"__main__.py#L26\" class=\"source_link\" style=\"float:right\">[source]</a></h4>\n",
       "\n",
       "> <code>CSVLogger.after_fit</code>()\n",
       "\n",
       "Close the file and clean up."
      ],
      "text/plain": [
       "<IPython.core.display.Markdown object>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "show_doc(CSVLogger.after_fit)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Export -"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Converted 00_torch_core.ipynb.\n",
      "Converted 01_layers.ipynb.\n",
      "Converted 01a_losses.ipynb.\n",
      "Converted 02_data.load.ipynb.\n",
      "Converted 03_data.core.ipynb.\n",
      "Converted 04_data.external.ipynb.\n",
      "Converted 05_data.transforms.ipynb.\n",
      "Converted 06_data.block.ipynb.\n",
      "Converted 07_vision.core.ipynb.\n",
      "Converted 08_vision.data.ipynb.\n",
      "Converted 09_vision.augment.ipynb.\n",
      "Converted 09b_vision.utils.ipynb.\n",
      "Converted 09c_vision.widgets.ipynb.\n",
      "Converted 10_tutorial.pets.ipynb.\n",
      "Converted 10b_tutorial.albumentations.ipynb.\n",
      "Converted 11_vision.models.xresnet.ipynb.\n",
      "Converted 12_optimizer.ipynb.\n",
      "Converted 13_callback.core.ipynb.\n",
      "Converted 13a_learner.ipynb.\n",
      "Converted 13b_metrics.ipynb.\n",
      "Converted 14_callback.schedule.ipynb.\n",
      "Converted 14a_callback.data.ipynb.\n",
      "Converted 15_callback.hook.ipynb.\n",
      "Converted 15a_vision.models.unet.ipynb.\n",
      "Converted 16_callback.progress.ipynb.\n",
      "Converted 17_callback.tracker.ipynb.\n",
      "Converted 18_callback.fp16.ipynb.\n",
      "Converted 18a_callback.training.ipynb.\n",
      "Converted 18b_callback.preds.ipynb.\n",
      "Converted 19_callback.mixup.ipynb.\n",
      "Converted 20_interpret.ipynb.\n",
      "Converted 20a_distributed.ipynb.\n",
      "Converted 21_vision.learner.ipynb.\n",
      "Converted 22_tutorial.imagenette.ipynb.\n",
      "Converted 23_tutorial.vision.ipynb.\n",
      "Converted 24_tutorial.siamese.ipynb.\n",
      "Converted 24_vision.gan.ipynb.\n",
      "Converted 30_text.core.ipynb.\n",
      "Converted 31_text.data.ipynb.\n",
      "Converted 32_text.models.awdlstm.ipynb.\n",
      "Converted 33_text.models.core.ipynb.\n",
      "Converted 34_callback.rnn.ipynb.\n",
      "Converted 35_tutorial.wikitext.ipynb.\n",
      "Converted 36_text.models.qrnn.ipynb.\n",
      "Converted 37_text.learner.ipynb.\n",
      "Converted 38_tutorial.text.ipynb.\n",
      "Converted 39_tutorial.transformers.ipynb.\n",
      "Converted 40_tabular.core.ipynb.\n",
      "Converted 41_tabular.data.ipynb.\n",
      "Converted 42_tabular.model.ipynb.\n",
      "Converted 43_tabular.learner.ipynb.\n",
      "Converted 44_tutorial.tabular.ipynb.\n",
      "Converted 45_collab.ipynb.\n",
      "Converted 46_tutorial.collab.ipynb.\n",
      "Converted 50_tutorial.datablock.ipynb.\n",
      "Converted 60_medical.imaging.ipynb.\n",
      "Converted 61_tutorial.medical_imaging.ipynb.\n",
      "Converted 65_medical.text.ipynb.\n",
      "Converted 70_callback.wandb.ipynb.\n",
      "Converted 71_callback.tensorboard.ipynb.\n",
      "Converted 72_callback.neptune.ipynb.\n",
      "Converted 73_callback.captum.ipynb.\n",
      "Converted 74_callback.cutmix.ipynb.\n",
      "Converted 97_test_utils.ipynb.\n",
      "Converted 99_pytorch_doc.ipynb.\n",
      "Converted dev-setup.ipynb.\n",
      "Converted index.ipynb.\n",
      "Converted quick_start.ipynb.\n",
      "Converted tutorial.ipynb.\n"
     ]
    }
   ],
   "source": [
    "#hide\n",
    "from nbdev.export import notebook2script\n",
    "notebook2script()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "jupytext": {
   "split_at_heading": true
  },
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
